﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Transactions;
using IBS.Dal;
using IBS.Model;
using IBS.Model.Base;
using IBS.ServiceContracts;
using IBS.Bll.Tools;
using log4net;

namespace IBS.Bll.Accounts
{

    //public static class BalanceHelper
    //{
    //    public static Balance CurrentBalance(this Account account)
    //    {
    //        return account.Balance.OrderByDescending(b => b.Date).FirstOrDefault(b => b.Date.Date == DateTime.Now.Date);
    //    }
    //}

    public class AccountService : ServiceBase, IAccountService
    {
        private readonly ICurrencyService _currencyService;

        #region Fields

        private static readonly ILog log = LogManager.GetLogger(typeof(AccountService));
        private AccountValidator _accountValidator;
        #endregion

        #region Constructors

        public AccountService(IIbsContext context, ICurrencyService currencyService)
            : base(context)
        {
            InstanceNumber++;
            _currencyService = currencyService;
            
            _accountValidator = new AccountValidator(context, this);
        }

        #endregion

        public static int InstanceNumber = 0;
        #region Maintenance

        public Account CreateAccount(Client client, string ISO, string login)
        {
            log.InfoFormat("CreateAccount(login =  {0}) is start ...", login);
            Account account = new Account();
            var accountCurrency = _currencyService.GetCurrency(ISO);
            try
            {
                log.InfoFormat("Creating empty account...");

                account.IsBlocked = false;
                account.UpdateLogin = login;
                account.CreateDate = DateTime.Now;
                account.Currency = accountCurrency;
                account.Client = client;
                account.Number = NumbersGenerator.GenerateAccountNumber();

                var balance = new Balance();
                balance.Date = DateTime.Now;
                account.Balance.Add(balance);
                client.Account.Add(account);

                _context.Accounts.AddObject(account);
                _context.Balances.AddObject(balance);
                _context.SaveChanges();

                //
                log.InfoFormat("Account has been created");
            }
            catch (Exception exception)
            {
                log.Error(exception.Message, exception);
                throw;
            }
            return account;
        }

        public void DeleteAccount(string accountNumber)
        {
            try
            {
                var account = GetAccountByNumber(accountNumber);
                _context.Accounts.DeleteObject(account);
                _context.SaveChanges();
                log.InfoFormat("DeleteAccount with id = {0} has been completed", account.Id);
            }
            catch (Exception exception)
            {
                log.Error(exception.Message, exception);
                throw;
            }
        }
       
        public void UpdateAccount(Account account, string login)
        {
            try
            {
                log.InfoFormat("UpdateAccount with id = {0} operator login = {1} is start ...", account.Id, login);
                account.UpdateLogin = login;
                _context.SaveChanges();
                log.InfoFormat("UpdateAccount with id = {0} has been completed", login);
            }
            catch (Exception exception)
            {
                log.Error(exception.Message, exception);
                throw;
            }
        }

        public void BlockAccount(string accountNumber, string login)
        {
            try
            {
                var account = GetAccountByNumber(accountNumber);
                log.InfoFormat("BlockAccount with id = {0} operator login = {1} is start ...", account.Id, login);
                if (account.IsBlocked)
                    throw new BusinessException("Account is already blocked");

                account.IsBlocked = true;
                UpdateAccount(account, login);

                log.InfoFormat("BlockAccount with id = {0} operator login = {1} is start ...", account.Id, login);
            }
            catch (Exception exception)
            {
                log.Error(exception.Message, exception);
                throw;
            }
        }

        public void UnBlockAccount(string accountNumber, string login)
        {
            try
            {
                var account = GetAccountByNumber(accountNumber);
                log.InfoFormat("UnBlockAccount with id = {0} operator login = {1} is start ...", account.Id, login);

                if (!account.IsBlocked)
                    throw new BusinessException("Account is already unblocked");
                account.IsBlocked = false;
                UpdateAccount(account, login);
            }
            catch (Exception exception)
            {
                log.Error(exception.Message, exception);
                throw;
            }
        }

        public IList<Account> GetClientAccounts(Client client, Func<Account, bool> predicate = null, int? page = null)
        {
            IList<Account> result;
            try
            {
                log.InfoFormat("GetClientAccountsByCriteria for clientId = {0} page = {1}",
                               client.Id,
                               page.HasValue
                                   ? page.Value.ToString()
                                   : "all");

                result = _context.Accounts
                    .GetByCriteria(page, predicate)
                    .Where(a => a.ClientId == client.Id).ToList();


                log.InfoFormat("GetClientAccountsByCriteria for clientId = {0} has been completed", client.Id);
            }
            catch (Exception exception)
            {
                log.Error(exception.Message, exception);
                throw;
            }
            return result;
        }

       
        public IList<Account> GetAccounts(Func<Account, bool> predicate = null, int? page = null)
        {
            IList<Account> result;
            try
            {

                result = _context.Accounts.GetByCriteria(page, predicate);
            }
            catch (Exception exception)
            {
                log.Error(exception.Message, exception);
                throw;
            }
            return result;
        }

        public Balance GetCurrentBalance(Account account)
        {
            try
            {
                log.InfoFormat("GetCurrentBalance with accountId = {0} ...", account.Id);
                var currentBalance = account.Balance.OrderByDescending(a=>a.Date).FirstOrDefault();

                if (currentBalance == null)
                    throw new BusinessException("Account doesn't have any balance");

                log.InfoFormat("CurrentBalance for Account with Id = {0} has been got", account.Id);
                return currentBalance;
            }
            catch (Exception exception)
            {
                log.Error(exception.Message, exception);
                throw;
            }
        }

        public Balance GetCurrentBalance(string accountNumber)
        {
            try
            {
                var account = GetAccountByNumber(accountNumber);
                log.InfoFormat("GetCurrentBalance with accountId = {0} ...", account.Id);
                var currentBalance = GetCurrentBalance(account);
                log.InfoFormat("CurrentBalance for Account with Id = {0} has been got", account.Id);
                return currentBalance;
            }
            catch (Exception exception)
            {
                log.Error(exception.Message, exception);
                throw;
            }
        }

        public bool AccountHasAmount(Account account, decimal amount)
        {
            var currentBalance = GetCurrentBalance(account);
            if (account.FreezableAmount < 0 || amount < 0)
                throw new BusinessException("You can't do this action");
            return currentBalance.Amount - account.FreezableAmount - amount >= 0;

        }
        #endregion;

        #region Accounts



        /// <summary>
        /// Счет ЕРИП 
        /// </summary>
        public Account EripAccount
        {
            get { return _context.Accounts.Single(a => a.Id == 21); }
        }

        /// <summary>
        /// Белоруские активы банка
        /// </summary>
        public Account AssetsAccount
        {
            get { return _context.Accounts.Single(a => a.Id == 1); }
        }

        /// <summary>
        /// Валютные активы банка
        /// </summary>
        public Account GAssetsAccount
        {
            get { return _context.Accounts.Single(a => a.Id == 2); }
        }

        private AccountsPair _usaSelling;

        /// <summary>
        /// Пара продажи USA
        /// </summary>
        public AccountsPair UsaSellingPair
        {
            get
            {
                return _usaSelling ?? (_usaSelling = new AccountsPair(_context.Accounts.Single(a => a.Id == 3),
                                                                      _context.Accounts.Single(a => a.Id == 4)));
            }

        }

        /// <summary>
        /// Пара покупки USA
        /// </summary>
        public AccountsPair UsaBuyingPair
        {
            get
            {
                return _usaSelling ?? (_usaSelling = new AccountsPair(_context.Accounts.Single(a => a.Id == 5),
                                                                      _context.Accounts.Single(a => a.Id == 6)));
            }
        }


        public Account GetAssetsAccount(string ISO)
        {
            switch (ISO)
            {
                case "USD":
                    return GAssetsAccount;
                case "BYR":
                    return AssetsAccount;
            }
            throw new BusinessException("Currency does not exist");
        }

        public AccountsPair GetSellingPair(string ISO)
        {
            switch (ISO)
            {
                case "USD":
                    return UsaSellingPair;
            }
            throw new BusinessException("Currency does not exist");
        }

        public AccountsPair GetBuyinPair(string ISO)
        {
            switch (ISO)
            {
                case "USD":
                    return UsaBuyingPair;
            }
            throw new BusinessException("Currency does not exist");
        }


        #endregion;

        public Account GetAccountByNumber(string accountNumber)
        {
            try
            {
                return _context.Accounts.Single(a => a.Number == accountNumber);
            }
            catch (InvalidOperationException)
            {
                throw new BusinessException("Account does not exist");
            }
        }

        public void ReserveAmount(string accountNumber, decimal amount)
        {
           
            var account = GetAccountByNumber(accountNumber);
            
            log.InfoFormat("Reserv is {1} Reserve {0} amount", amount, account.FreezableAmount);
            //account.FreezableAmount += amount;
        }


       
        public void UnReserveAmount(string accountNumber, decimal amount)
        {
            var account = GetAccountByNumber(accountNumber);
            log.InfoFormat("Reserv is {1} UNReserve {0} amount", amount, account.FreezableAmount);
           // account.FreezableAmount -= amount;
        }
    }
}
